package com.jpa.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.jpa.entity.sign.NmBaseProjectDbinfo;
import com.jpa.repository.sign.NmBaseProjectDbinfoRepository;
import com.jpa.service.NmBaseProjectDbinfoService;
import com.jpa.util.DBBase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyNameAliases;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * 动态注册数据源
 * 启动动态数据源请在启动类中（如SpringBootSampleApplication）
 * 添加 @Import(DynamicDataSourceRegister.class)
 */
@Configuration
@EnableJpaRepositories(
        basePackages = "com.jpa.repository.dy",
        entityManagerFactoryRef =  "localContainerEntityManagerFactoryBean",
        transactionManagerRef = "platformTransactionManager"
)
@Order(10)
public class DynamicDataSourceRegister {

    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);

    /**
     * 别名
     */
    public final static ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();

    /**
     * 由于部分数据源配置不同，所以在此处添加别名，避免切换数据源出现某些参数无法注入的情况
     */
    static {
        aliases.addAliases("url", new String[]{"jdbc-url"});
        aliases.addAliases("username", new String[]{"user"});
    }

    //配置上下文（也可以理解为配置文件的获取工具）
    @Autowired
    private Environment evn;


    @Bean("dynamicDataSource")
    public DataSource dynamicDataSource(){
        DataSource defaultDatasource = null;
//        List<NmBaseProjectDbinfo> all = nmBaseProjectDbinfoRepository.findAll();

        Binder binder = Binder.get(evn);
        Map configs = binder.bind("jpa.dynamic-data-source.db1", Map.class).get();
        Properties properties = new Properties();
        properties.put("username", configs.get("username"));
        properties.put("password", String.valueOf(configs.get("password")));
        properties.put("driver-class-name","com.mysql.cj.jdbc.Driver");
        properties.put("url", configs.get("url"));
        DBBase.init(properties);
        String sql = "select id, db_address as dbAddress, db_name as dbName, db_user as dbUser, db_password as dbPassword from nm_base_project_dbinfo ";
        DBBase dbBase = new DBBase();
        List<NmBaseProjectDbinfo> all = dbBase.queryForObjectList(sql, NmBaseProjectDbinfo.class);
        Map<Object, Object> customDataSources = new HashMap<>();
        if (!CollectionUtils.isEmpty(all)) {
            for (NmBaseProjectDbinfo dbinfo : all) {
                logger.info("AfterRunner加载的数据源配置是: {}", dbinfo);
                Map config = new HashMap();
                config.put("username", dbinfo.getDbUser());
                config.put("password",dbinfo.getDbPassword());
                config.put("driver-class-name","com.mysql.cj.jdbc.Driver");
                config.put("url",dbinfo.getDbAddress());
                //设置从数据源
                Class<? extends DataSource> clazz = getDataSourceType("com.alibaba.druid.pool.DruidDataSource");
                // 绑定参数
                DataSource consumerDatasource = bind(clazz, config);
                // 获取数据源的key，以便通过该key可以定位到数据源
                customDataSources.put(dbinfo.getDbName(), consumerDatasource);
                // 数据源上下文，用于管理数据源与记录已经注册的数据源key
                DynamicDataSourceContextHolder.dataSourceIds.add(dbinfo.getDbName());
                logger.info("注册数据源{}成功", dbinfo.getDbName());
            }
        }
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setTargetDataSources(customDataSources);
        DynamicDataSourceContextHolder.setDataSourceType(all.get(0).getDbName());
        dynamicDataSource.setDefaultTargetDataSource(defaultDatasource);
        logger.info("注册数据源成功，一共注册{}个数据源", customDataSources.keySet().size());
        return dynamicDataSource;
    }
    /**
     * 通过字符串获取数据源class对象
     *
     * @param typeStr
     * @return
     */
    private Class<? extends DataSource> getDataSourceType(String typeStr) {
        Class<? extends DataSource> type;
        try {
            if (StringUtils.hasLength(typeStr)) {
                // 字符串不为空则通过反射获取class对象
                type = (Class<? extends DataSource>) Class.forName(typeStr);
            } else {
                // 默认为Druid数据源
                type = DruidDataSource.class;
            }
            return type;
        } catch (Exception e) {
            throw new IllegalArgumentException("can not resolve class with type: " + typeStr); //无法通过反射获取class对象的情况则抛出异常，该情况一般是写错了，所以此次抛出一个runtimeexception
        }
    }

    private <T extends DataSource> T bind(Class<T> clazz, Map properties) {
        ConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
        Binder binder = new Binder(new ConfigurationPropertySource[]{source.withAliases(aliases)});
        // 通过类型绑定参数并获得实例对象
        return binder.bind(ConfigurationPropertyName.EMPTY, Bindable.of(clazz)).get();
    }

    @Bean(name = "localContainerEntityManagerFactoryBean")
    public LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setDatabasePlatform("org.hibernate.dialect.MySQL5InnoDBDialect");
        vendorAdapter.setShowSql(true);
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setPackagesToScan("com.jpa.entity.dy");
        factory.setDataSource(dynamicDataSource());
        factory.setJpaVendorAdapter(vendorAdapter);
        return factory;
    }

    @Bean(name = "platformTransactionManager")
    public PlatformTransactionManager platformTransactionManager() {
        return new JpaTransactionManager(localContainerEntityManagerFactoryBean().getObject());
    }
}